/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2021年6月6日
 */
package com.massyframework.beanskin.runtime.framework;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.lang3.exception.ExceptionUtils;

import com.massyframework.beanskin.Module;
import com.massyframework.beanskin.ModuleException;
import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.assembly.AssemblyConfig;
import com.massyframework.beanskin.assembly.AssemblyContext;
import com.massyframework.beanskin.assembly.DependencyMatcher;
import com.massyframework.beanskin.customer.Customer;
import com.massyframework.beanskin.fragment.Fragment;
import com.massyframework.beanskin.framework.Framework;
import com.massyframework.beanskin.library.Library;
import com.massyframework.beanskin.plugin.Plugin;
import com.massyframework.beanskin.service.DependencyServiceDescriptor;
import com.massyframework.beanskin.service.ServiceReference;
import com.massyframework.beanskin.service.ServiceRepository;
import com.massyframework.beanskin.service.awaitstrategy.AwaitServiceDescriptor;
import com.massyframework.beanskin.service.awaitstrategy.AwaitStrategy;
import com.massyframework.beanskin.service.awaitstrategy.AwaitStrategyRepository;
import com.massyframework.beanskin.util.exceptioncollect.ExceptionCollectUtils;
import com.massyframework.beanskin.util.exceptioncollect.ExceptionGroup;

/**
 * 控制台输出
 * @author huangkh
 *
 */
public class ConsoleReporter {

	/**
	 * 
	 */
	public ConsoleReporter() {
		
	}
	
	public void printReport(Framework framework) {
		Objects.requireNonNull(framework, "\"framework\" cannot be null.");
		
		StringBuilder builder = new StringBuilder();
		this.reportTitle(builder);
		this.reportCustomer(builder, framework);
		
		Assembly kernel = framework.findModuleById(0,  Assembly.class);
		this.reportInitParams(kernel, builder);
		this.reportKernelBeans(kernel, builder);
		this.reportServices(kernel, builder);
		
		SortComparator comp = new SortComparator();		
		List<Module<?>> modules = framework.getModules();
		Collections.sort(modules, comp);
		this.reportModules(modules, builder);
		this.reportAwaitStrategys(modules, ServiceRepository.retrieveFrom(kernel), builder);
		this.reportExceptions(builder);
		this.reportTail(builder);

		System.out.println(builder.toString());
	}

	protected void reportTitle(StringBuilder builder) {
		builder.append("[INFO] ------------------------------------------------------------").append("\r\n")
				.append("[INFO] Framework Status Reporting ").append("\r\n")
				.append("[INFO] ------------------------------------------------------------").append("\r\n");
	}
	
	protected void reportCustomer(StringBuilder builder, Framework framework) {
		Assembly kernel = framework.findModuleById(0, Assembly.class);
		Customer customer = kernel.getAssemblyContext().getBean(Customer.class.getName(), Customer.class);
		
		builder.append("[INFO] ").append("\r\n")
			   .append("[INFO] Customer[ID:").append(customer.getId()).append("] ")
			   .append(customer.getName());
		builder.append("\r\n")
			.append("[INFO] ------------------------------------------------------------").append("\r\n");
	}

	protected void reportTail(StringBuilder builder) {
		builder.append("[INFO] Framework Started.").append("\r\n")
				.append("[INFO] ------------------------------------------------------------").append("\r\n");
	}

	protected void reportModules(List<Module<?>> modules, StringBuilder builder) {
		builder.append("[INFO] ").append("\r\n").append("[INFO] Found modules:").append("\r\n");
		for (Module<?> module: modules) {
			String identifier = module.toIdentifier();
			String name = module.getConfig().getName();
						
			if (module instanceof Library) {
				String message = String.format("%3d) [Library ] %-80s name = %s ", module.getId(), identifier, name);
				builder.append("[INFO] ").append(message).append("\r\n");
				continue;
			}
			
			
			if (module instanceof Plugin) {
				String message = String.format("%3d) [Plugin  ] %-80s name = %s", module.getId(), identifier, name);
				builder.append("[INFO] ").append(message).append("\r\n");
				continue;
			}
			
			if (module instanceof Assembly) {
				Assembly assembly = (Assembly)module;
				String text = assembly.getAssemblyStatus().toString();
				identifier = identifier.concat(" [").concat(text).concat("]");
				String message = String.format("%3d) [Assembly] %-80s name = %s" , module.getId(), identifier, name);
				builder.append("[INFO] ").append(message).append("\r\n");
				
				DependencyMatcher matcher = assembly.adapt(DependencyMatcher.class);
				if (matcher != null) {
					Collection<DependencyServiceDescriptor<?>> dependencies  = matcher.getUnmatchDependencyServiceDescriptors();
					for (DependencyServiceDescriptor<?> descriptor : dependencies) {
						String msg = descriptor.getFilterString() != null
								? String.format("-- require: class= %s, filterString= %s",
										descriptor.getRequiredType().getName(), descriptor.getFilterString())
								: String.format("-- require: class= %s", descriptor.getRequiredType().getName());
						builder.append("[INFO]     ").append(msg).append("\r\n");
					}
				}
			}
			
			if (module instanceof Fragment) {
				Fragment fragment = (Fragment)module;
				String message = String.format("%3d) [Fragment] %-80s name = %s", fragment.getId(), identifier, name);
				builder.append("[INFO] ").append(message).append("\r\n");
				continue;
			}
		}
		
		builder.append("[INFO] ------------------------------------------------------------").append("\r\n");
	}
	
	
	/**
	 * 报告初始化参数
	 * 
	 * @param assembly
	 */
	protected void reportInitParams(Assembly assembly, StringBuilder builder) {
		builder.append("[INFO]").append("\r\n").append("[INFO] Init Parameters:").append("\r\n");
		AssemblyConfig config = assembly.getConfig();
		List<String> names = config.getParameterNames();
		Collections.sort(names);
		int size = names.size();
		for (int i = 0; i < size; i++) {
			String name = names.get(i);
			String value = config.getParameter(name);
			value = value.replace("\r\n", "  ");
			value = value.replace("\n", "  ");
			String message = String.format("%3d) %s = %s", i, name, "\"" + value + "\"");
			builder.append("[INFO] ").append(message).append("\r\n");
		}
		builder.append("[INFO] ------------------------------------------------------------").append("\r\n");
	}

	/**
	 * 报告内核Bean
	 * 
	 * @param assembly
	 */
	protected void reportKernelBeans(Assembly assembly, StringBuilder builder) {
		AssemblyContext context = assembly.getAssemblyContext();
		List<String> names = context.getBeanNames();
		Collections.sort(names);

		builder.append("[INFO]  ").append("\r\n");
		builder.append("[INFO] Kernel Assembly Components:").append("\r\n");
		int size = names.size();

		for (int i = 0; i < size; i++) {
			String name = names.get(i);
			Object service = context.getBean(name);
			String message = String.format("%3d) name=%-86s classes = %s", i, name, service.getClass().getName());
			builder.append("[INFO] ").append(message).append("\r\n");
		}
		builder.append("[INFO] ------------------------------------------------------------").append("\r\n");
		
	}

	/**
	 * 报告注册的服务
	 * 
	 * @param assembly {@link Assembly} 装配件
	 * @param builder  {@link StringBuilder},字符串构建器
	 */
	protected void reportServices(Assembly assembly, StringBuilder builder) {
		ServiceRepository repo = ServiceRepository.retrieveFrom(assembly);

		List<Class<?>> serviceTypes = repo.getServiceTypes();
		if (serviceTypes.isEmpty()) {
			return;
		}

		Collections.sort(serviceTypes, new ServiceTypeSore());
		builder.append("[INFO]  ").append("\r\n");
		builder.append("[INFO] registed ServiceTypes:").append("\r\n");

		int size = serviceTypes.size();
		for (int i = 0; i < size; i++) {
			Class<?> serviceType = serviceTypes.get(i);
			String name = serviceType.getName();

			String serviceNames = null;
			List<ServiceReference<?>> references = this.getServiceReferences(serviceType, repo);
			if (!references.isEmpty()) {
				for (ServiceReference<?> reference : references) {
					String serviceName = reference.getServiceName();
					if (serviceName != null) {
						serviceNames = serviceNames == null ?
								serviceName :
								serviceNames.concat(",").concat(serviceName);
					}
				}
			}
			
			String message =  null;
			if (serviceNames == null) {
				message = String.format("%3d) type=%-86s", i, name, serviceNames);
			}else {
				message = String.format("%3d) type=%-86s name = %s", i, name, serviceNames);
			}

			builder.append("[INFO] ").append(message).append("\r\n");
		}
		builder.append("[INFO] ------------------------------------------------------------").append("\r\n");
	}

	/**
	 * 
	 * @param serviceType
	 * @param repository
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private List<ServiceReference<?>> getServiceReferences(Class<?> serviceType, ServiceRepository repository) {
		List<?> result = repository.getServiceReferences(serviceType);
		return (List<ServiceReference<?>>) result;
	}
	
	/**
	 * 输出等待服务情况
	 * @param builder {@link StringBuilder}
	 * @param repository {@link ServiceRepository}
	 */
	protected void reportAwaitStrategys(List<Module<?>> modules, 
			ServiceRepository repository, StringBuilder builder) {
		AwaitStrategyRepository asrepo = repository.getService(AwaitStrategyRepository.class);
		Map<Module<?>, List<AwaitStrategy>> map = asrepo.getAwaitStrategies();
		builder.append("[INFO]  ").append("\r\n");
		if (map.isEmpty()) {
			builder.append("[INFO] Await Strategy:  (NONE) ").append("\r\n");
			builder.append("[INFO] ------------------------------------------------------------").append("\r\n");
			return;
		}
		
		builder.append("[INFO] Await Strategies:     ").append("\r\n");
		int index = 1;
		for (Module<?> module: modules) {
			List<AwaitStrategy> list = map.get(module);
			if (list != null) {
				String identifier = module.toIdentifier();
				String name = module.getConfig().getName();
										
				if (module instanceof Plugin) {
					String message = String.format("%3d) [Plugin  ] %-80s name = %s", index, identifier, name);
					builder.append("[INFO] ").append(message).append("\r\n");
				}else {
					if (module instanceof Assembly) {
						String message = String.format("%3d) [Assembly] %-80s name = %s", index, identifier, name);
						builder.append("[INFO] ").append(message).append("\r\n");
					}else {
						if (module instanceof Library) {
							String message = String.format("%3d) [Library ] %-80s name = %s ", index, identifier, name);
							builder.append("[INFO] ").append(message).append("\r\n");
						}else {
							String message = String.format("%3d) [Fragment] %-80s name = %s ", index, identifier, name);
							builder.append("[INFO] ").append(message).append("\r\n");
						}
					}
				}
				
				
				for (AwaitStrategy strategy: list) {
					Collection<AwaitServiceDescriptor<?>> descriptors = strategy.getUnmatchs();
					for (AwaitServiceDescriptor<?> desc: descriptors) {
						String message = String.format("     -- %-80s filterString = %s", desc.getRequiredType().getName(), desc.getFilterString());
						builder.append("       ").append(message).append("\r\n");
					}
				}
				
				index ++;
			}
		}
		
		builder.append("[INFO] ------------------------------------------------------------").append("\r\n");
	}
	
	/**
	 * 输出例外
	 * @param builder
	 */
	protected void reportExceptions(StringBuilder builder) {
		ExceptionGroup exceptions = ExceptionCollectUtils.unbind();
		if (exceptions == null) return;
		
		builder.append("[INFO]  ").append("\r\n");
		List<Exception> list = exceptions.removeAll();
		if (list.isEmpty()) {
			builder.append("[INFO] Collect Exceptions:  (NONE) ").append("\r\n");
			builder.append("[INFO] ------------------------------------------------------------").append("\r\n");
			return;
		}
		
		builder.append("[INFO] Collect Exceptions:     ").append(list.size()).append("\r\n");
		for (int i=0; i<list.size(); i++) {
			Exception e = list.get(i);
			String message =  null;
			String positioning = null;
			if (e instanceof ModuleException) {
				ModuleException mex = (ModuleException)e;
				positioning = mex.getModuleIdentifier().toIdentifier();
			}else {
				positioning = "[Unknown]";
			}
			
			
			Throwable cause = ExceptionUtils.getRootCause(e);
			message = String.format("%3d) Position %s" , i, positioning); 
			builder.append(message).append("\r\n");
			message = ExceptionUtils.getStackTrace(cause);
			builder.append("     ").append(message);
			
			if (i != list.size() -1) {
				builder.append("\r\n");
			}
		}
		builder.append("[INFO] ------------------------------------------------------------").append("\r\n");
	}

	private class ServiceTypeSore implements Comparator<Class<?>> {

		@Override
		public int compare(Class<?> o1, Class<?> o2) {
			String name1 = o1.getName();
			String name2 = o2.getName();
			return name1.compareTo(name2);
		}
		
	}
	
	private class SortComparator implements Comparator<Module<?>> {

		@Override
		public int compare(Module<?> o1, Module<?> o2) {
			return Long.compare(o1.getId(), o2.getId());
		}
		
	}

}
